using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Net.Sockets;
using System.Net;
//#define DEBUG_CAMERA

namespace edu.stanford.hci.ideas
{

    public class CameraData
    {
        public int x;
        public int y;
        public int radius;

        public CameraData()
        {        
           x=-1;
           y=-1;
           radius= -1;

        }

    }
    public class Camera
    {
        private static int MAX_FACES = 4;
        private static int MAX_BLOBS = 10;

        private UdpPacket udpPacket;
        private Osc oscUdp;
        private OscMessage msgSent;
        private string rawData;
        private List<CameraListener> cameraListeners = new List<CameraListener>();
        private int numFacesReported=0;
        private int numBlobsReported =0;
        private CameraData[] faces = new CameraData[MAX_FACES];
        private CameraData[] blobs = new CameraData[MAX_BLOBS];


         public Camera()
         {
             UdpClient udpClient = new UdpClient(7000);
             IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
             Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
             //            //string returnData = Encoding.ASCII.GetString(receiveBytes);
             //            ArrayList messages = Osc.PacketToOscMessages(receiveBytes, receiveBytes.GetUpperBound(0));

             udpPacket = new UdpPacket();
             oscUdp = new Osc(udpPacket);
             // A thread is started when the Osc object is created to read 
             // incoming messages.
            
             oscUdp.SetAddressHandler("/faces", FacesHandler);
             oscUdp.SetAddressHandler("/face", FaceHandler);
             oscUdp.SetAddressHandler("/blobs", BlobsHandler);
             oscUdp.SetAddressHandler("/blob", BlobHandler);
             //oscUdp.SetAllMessageHandler(OSCMessageHandler);
         }
        
         // The method you specified as the handler will be called back when a 
         // message with a matching address string comes back from the board.
         public void OSCMessageHandler(OscMessage oscMessage)
         {
             // write the message to a console, for example
             string lastMessage = Osc.OscMessageToString(oscMessage);
             msgSent = oscMessage;
#if DEBUG_CAMERA
             Console.WriteLine("\nOscMessage > " + lastMessage);
#endif
             fireCameraEvent();
         }

        public void FacesHandler(OscMessage oscMessage)
        {
            msgSent = oscMessage;
            if ((int)msgSent.Values[0] > MAX_FACES) numFacesReported = MAX_FACES;
            else numFacesReported = (int)msgSent.Values[0];
#if DEBUG_CAMERA
            Console.WriteLine("\nNumFacesReported > " + numFacesReported);
#endif
            fireFacesEvent();
        }

        public void FaceHandler(OscMessage oscMessage)
        {
            msgSent = oscMessage;

                int faceNum = (int)msgSent.Values[0];
                faces[faceNum] = new CameraData();
                faces[faceNum].x = (int)msgSent.Values[1];
                faces[faceNum].y = (int)msgSent.Values[2];
                faces[faceNum].radius = (int)msgSent.Values[3];
#if DEBUG_CAMERA
                Console.WriteLine("FaceNum" + faceNum + ": (" + faces[faceNum].x + "," + faces[faceNum].y + "," + faces[faceNum].radius + ")");
#endif
            fireFaceEvent();
        }
        public void BlobsHandler(OscMessage oscMessage)
        {
            msgSent = oscMessage;
            if ((int)msgSent.Values[0] > MAX_BLOBS) numBlobsReported = MAX_BLOBS;
            else numBlobsReported = (int)msgSent.Values[0];
#if DEBUG_CAMERA
            Console.WriteLine("\nNumBlobsReported > " + numBlobsReported);
#endif
            fireBlobsEvent();
        }

        public void BlobHandler(OscMessage oscMessage)
        {
            msgSent = oscMessage;
            int blobNum = (int)msgSent.Values[0];
            blobs[blobNum] = new CameraData();
            blobs[blobNum].x = (int)msgSent.Values[1];
            blobs[blobNum].y = (int)msgSent.Values[2];
            blobs[blobNum].radius = (int)msgSent.Values[3];
#if DEBUG_CAMERA
            Console.WriteLine("BlobNum" + blobNum + ": (" + blobs[blobNum].x + "," + blobs[blobNum].y + "," + blobs[blobNum].radius + ")");
#endif
            fireBlobEvent();
        }

        private void updateCameraData()
        {
            Console.WriteLine("update camera data");
            fireCameraEvent();
        }
         public string getCameraData()
         {
             return rawData;
         }

         public void setCameraData(string message)
         {
             rawData = message;
             updateCameraData();
             fireCameraEvent();
         }

        public void addCameraListener(CameraListener listener)
        {
            cameraListeners.Add(listener);
        }
        public void fireCameraEvent()
        {
            CameraEvent e;
            if (numFacesReported<0)  e = new CameraEvent();
            else e = new CameraEvent(numFacesReported, faces, numBlobsReported, blobs, msgSent);
          
            foreach (CameraListener listener in cameraListeners)
            {
                listener.cameraEvent(e);
            }
        }
        public void fireFaceEvent()
        {
            CameraEvent e = new CameraEvent(numFacesReported, faces, numBlobsReported, blobs, msgSent);
            foreach (CameraListener listener in cameraListeners)
            {
                listener.sawFace(e);
            }
        }
        public void fireFacesEvent()
        {
            CameraEvent e = new CameraEvent(numFacesReported, faces, numBlobsReported, blobs, msgSent); 
            foreach (CameraListener listener in cameraListeners)
            {
                listener.sawFaces(e);
            }
        }
        public void fireBlobEvent()
        {
            CameraEvent e = new CameraEvent(numFacesReported, faces, numBlobsReported, blobs, msgSent);
            foreach (CameraListener listener in cameraListeners)
            {
                listener.sawBlob(e);
            }
        }

        public void fireBlobsEvent()
        {
            CameraEvent e = new CameraEvent(numFacesReported, faces, numBlobsReported, blobs, msgSent);
            foreach (CameraListener listener in cameraListeners)
            {
                listener.sawBlobs(e);
            }
        }
    }
    public class CameraEvent
    {
        private DateTime timeStamp;
        public static int MAX_FACES = 4;
        public static int MAX_BLOBS = 10;

        private CameraData[] faces = new CameraData[MAX_FACES];
        private int numFaces = 0;
        private CameraData[] blobs = new CameraData[MAX_BLOBS];
        private int numBlobs = 0;
        private string rawData = null;

        // xxx code below does not make sense if we are only able to receive individual face events:
            
        public CameraEvent(int numFacesReported, CameraData[] facesReported, int numBlobsReported, CameraData[] blobsReported, OscMessage msgSent)
        {
           
            timeStamp = System.DateTime.Now;
            numFaces = numFacesReported;
            faces = facesReported;
            numBlobs = numBlobsReported;
            blobs = blobsReported;
            rawData = Osc.OscMessageToString(msgSent);
        }

        public CameraEvent()
        {
            //creates a null cameraEvent
            timeStamp = System.DateTime.Now;
            numFaces = 0;
            CameraData[] faces = new CameraData[MAX_FACES];
            for (int i = 0; i < faces.Length; i++) {
                faces[i] = new CameraData();
            }
            numBlobs = 0;
            CameraData[] blobs = new CameraData[MAX_BLOBS];
            for (int i = 0; i < blobs.Length; i++)
            {
                blobs[i] = new CameraData();
            }
            rawData = "";
        }

        public int getMaxFaces()
        {
            return MAX_FACES;
        }
        public int getMaxBlobs() { return MAX_BLOBS; }

        public DateTime getTimeStamp()
        {
            return timeStamp;
        }

        public int getNumFaces()
        {
            return numFaces;
        }

        public int getFaceX(int numFaces)
        {
           return faces[numFaces].x;
        }

        public int[] getFaceX(){
            int[] faceX= new int[MAX_FACES];
            for (int i=0; i<numFaces; i++){
                faceX[i] = faces[i].x;
            }
            for (int i = numFaces; i<MAX_FACES; i++){
                faceX[i] = -1;
            }
            return faceX;
        }

        public int getFaceY(int numFaces)
        {
            return faces[numFaces].y;
        }
        public int[] getFaceY()
        {
            int[] faceY = new int[MAX_FACES];
            for (int i = 0; i < numFaces; i++)
            {
                faceY[i] = faces[i].y;
            }
            for (int i = numFaces; i < MAX_FACES; i++)
            {
                faceY[i] = -1;
            }
            return faceY;
        }
        public int getFaceRadius(int numFaces)
        {
            return faces[numFaces].radius;
        }
        public int[] getFaceRadius()
        {
            int[] faceR = new int[MAX_FACES];
            for (int i = 0; i < numFaces; i++)
            {
                faceR[i] = faces[i].radius;
            }
            for (int i = numFaces; i < MAX_FACES; i++)
            {
                faceR[i] = -1;
            }
            return faceR;
        }
        public int getNumBlobs()
        {
            return numBlobs;
        }
        public int getBlobX(int numBlobs)
        {
            return blobs[numBlobs].x;
        }

        public int[] getBlobX()
        {
            int[] blobX = new int[MAX_BLOBS];
            for (int i = 0; i < numBlobs; i++)
            {
                blobX[i] = blobs[i].x;
            }
            for (int i = numBlobs; i < MAX_BLOBS; i++)
            {
                blobX[i] = -1;
            }
            return blobX;
        }
        public int getBlobY(int numBlobs)
        {
            return blobs[numBlobs].y;
        }
        public int[] getBlobY()
        {
            int[] blobY = new int[MAX_BLOBS];
            for (int i = 0; i < numBlobs; i++)
            {
                blobY[i] = blobs[i].y;
            }
            for (int i = numBlobs; i < MAX_BLOBS; i++)
            {
                blobY[i] = -1;
            }
            return blobY;
        }
        public int getBlobRadius(int numBlobs)
        {
            return blobs[numBlobs].radius;
        }
        public int[] getBlobRadius()
        {
            int[] blobR = new int[MAX_BLOBS]; ;
            for (int i = 0; i < numBlobs; i++)
            {
                blobR[i] = blobs[i].radius;
            }
            for (int i = numBlobs; i < MAX_BLOBS; i++)
            {
                blobR[i] = -1;
            }
            return blobR;
        }

        public string getRawData()
        {
            return rawData;
        }
    }

    public interface CameraListener
    {
        void sawFace(CameraEvent e);
        void sawFaces(CameraEvent e);
        void sawBlob(CameraEvent e);
        void sawBlobs(CameraEvent e);
        void cameraEvent(CameraEvent e);
    }
}
